package es.deusto.programacionIV.calculator.model;

/**
 * This class represents any binary operation.
 * It is an abstract class because it only manages the basic concepts common to any
 * binary operation such as the management of the operands, the retrieval of the result,
 * the storage of the operation type, the conversion of the operation to a string, etc.
 * The calculation itself cannot be done in this class because its a base binary operation 
 * class. Subclasses will have to implement the calculation abstract method declared in this class.
 * 
 * @author Iker Jamardo Zugaza
 *
 */
public abstract class Operation
{
	private OperationType type = null;
	private float operand1 = 0;
	private float operand2 = 0;
	private float result = 0;
	private boolean dirty = false;
	
	public Operation(OperationType type)
	{
		if (type == null)
		{
			throw new NullPointerException("The given type cannot be null.");
		}
		this.type = type;
	}
	
	/**
	 * Sets the operand 1 for the operation.
	 * @param operand1 The operand 1 for the operation.
	 */
	public void setOperand1(float operand1)
	{
		dirty = dirty || this.operand1 != operand1;
		this.operand1 = operand1;
	}
	
	/**
	 * Returns the operand 1 of the operation.
	 * @return The operand 1 of the operation.
	 */
	public float getOperand1()
	{
		return operand1;
	}
	
	/**
	 * Sets the operand 2 for the operation.
	 * @param operand2 The operand 2 for the operation.
	 */
	public void setOperand2(float operand2)
	{
		dirty = dirty || this.operand2 != operand2;
		this.operand2 = operand2;
	}
	
	/**
	 * Returns the operand 2 of the operation.
	 * @return The operand 2 of the operation.
	 */
	public float getOperand2()
	{
		return operand2;
	}

	/**
	 * Returns the operation type.
	 * @return The operation type.
	 */
	public OperationType getType()
	{
		return type;
	}
	
	/**
	 * Calculates the operation storing the result. Call getResult to obtain it.
	 */
	public void calculate()
	{
		if (dirty)
		{
			result = internalCalculate();
			dirty = false;
		}
	}
	
	/**
	 * Returns the result of the operation.
	 * @return The result of the operation.
	 */
	public float getResult()
	{
		if (dirty)
		{
			calculate();
		}
		return result;
	}
	
	@Override
	public String toString()
	{
		if (dirty)
		{
			calculate();
		}
		return "" + operand1 + " " + type.getSymbol() + " " + operand2 + " = " + result; 
	}
	
	@Override
	public boolean equals(Object object)
	{
		if (this == object) return true;
		boolean result = false;
		if (object instanceof Operation)
		{
			Operation operation = (Operation)object;
			result = this.operand1 == operation.operand1 && this.operand2 == operation.operand2 && this.type.equals(operation.type);
		}
		return result;
	}
	
	/**
	 * This protected method calculates the specific operation. It is called from the
	 * calculate method in this class and it return the result to do not let the 
	 * subclass access the private result attribute. 
	 * @return The result of the calculation of the operation.
	 */
	protected abstract float internalCalculate();
}
